package cn.edu.cuit.elena.transaction;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import cn.edu.cuit.elena.common.Configuration;
import cn.edu.cuit.elena.common.DuplicateException;
import cn.edu.cuit.elena.common.SackConstants;
import cn.edu.cuit.elena.db.DataSource;
import cn.edu.cuit.elena.db.DataSourceFactory;
import cn.edu.cuit.elena.db.dao.BaseDao;
import cn.edu.cuit.elena.trace.Tracer;
import cn.edu.cuit.elena.transaction.system.UserContext;

public class DataSourceManager
{
    private static BaseDao baseDao;
    static
    {
        Configuration configuration = Configuration.instance();
        baseDao = new BaseDao( DataSourceFactory.createDataSourceByConfiguration( configuration ) );
    }

    private Set<DataSource> dataSources;
    private UserContext userContext;

    private DataSourceManager(UserContext userContext)
    {
        this.userContext = userContext;
    }

    public static DataSourceManager createDataSourceManagerForUser( UserContext userContext )
    {
        if( userContext.getDataSourceManager() != null )
        {
            return null;
        }

        DataSourceManager dataSourceManager = new DataSourceManager( userContext );
        synchronize( dataSourceManager );
        userContext.setDataSourceManager( dataSourceManager );
        return dataSourceManager;
    }

    /**
     * This need be involved after datasource operation.
     * */
    private static void synchronize( DataSourceManager dataSourceManager )
    {
        if( dataSourceManager.dataSources == null )
        {
            loadFromDB( dataSourceManager );

            return;
        }

        ArrayList<String> parameters = new ArrayList<String>();
        parameters.add( dataSourceManager.getUserContext().getUserName() );
        List<List<String>> result = baseDao.queryForList( SackConstants.SQL.SELECT_DATASOURCES_OF_USER.sql(),
            parameters );

        Set<DataSource> dataSourcesCopy = new HashSet<DataSource>();
        dataSourcesCopy.addAll( dataSourceManager.dataSources );

        db_loop:
        for( List<String> row : result )
        {
            //Order by "url, username, password,driver"
            //datasourceString from DB
            String dataSourceString = row.get( 0 ) + row.get( 1 ) + row.get( 2 ) + row.get( 3 );

            for( DataSource dataSource : dataSourcesCopy )
            {
                //tempString from memory
                String tempString = dataSource.toString();
                //datasourceString from DB
                if( dataSource.toString().equals( dataSourceString ) )
                {
                    //if ds not only in menory but also in db, 
                    //remove the copy in temporary list.
                    dataSourcesCopy.remove( dataSource );
                    continue db_loop;
                }
            }

            Tracer.debugTrace(
                DataSourceManager.class,
                "DataSourceManager synchronization delete : " + dataSourceManager.userContext.getUserName() + " "
                    + row.get( 1 ) + " " + row.get( 0 ) + " " + row.get( 2 ) + " " + row.get( 3 ), null );
            //Delete dataBase order by "where userid=? and username=? and url=? and password=? and driver = ?;"
            baseDao.del(
                SackConstants.SQL.DELETE_DATASOURCE_OF_USER.sql(),
                Arrays.asList( new String[] { dataSourceManager.userContext.getUserName(), row.get( 1 ), row.get( 0 ),
                        row.get( 2 ), row.get( 3 ) } ) );
        }

        //datasourceCory from memory, if ds in memory but not in db, insert it.
        for( DataSource dataSource : dataSourcesCopy )
        {
            Tracer.debugTrace( DataSourceManager.class,
                "DataSourceManager synchronization add : " + dataSource.toString(), null );
            //Insert dataBase, order by  "userid,url,username,password,driver,state,schemaname,vender"
            baseDao.insertOneRow(
                SackConstants.SQL.INSERT_DATASOURCE_OF_USER.sql(),
                Arrays.asList( new String[] { dataSourceManager.userContext.getUserName(), dataSource.getUrl(),
                        dataSource.getUserName(), dataSource.getPassWord(), dataSource.getDriver(),
                        dataSource.getState(), dataSource.getName(), dataSource.getVender() } ) );
        }
    }

    private static void loadFromDB( DataSourceManager dataSourceManager )
    {

        dataSourceManager.dataSources = new HashSet<DataSource>();

        //Order by "url, username, password,driver"
        List<Map<String, String>> rows = baseDao.queryForMap( SackConstants.SQL.SELECT_DATASOURCES_OF_USER.sql(),
            dataSourceManager.userContext.getUserName() );

        for( Map<String, String> row : rows )
        {
            Map<String, String> map = new HashMap<String, String>();
            map.put( SackConstants.URL, row.get( SackConstants.URL ) );
            map.put( SackConstants.USER_NAME, row.get( SackConstants.USER_NAME ) );
            map.put( SackConstants.PASS_WORD, row.get( SackConstants.PASS_WORD ) );
            map.put( SackConstants.DRIVER, row.get( SackConstants.DRIVER ) );
            map.put( SackConstants.VENDER, row.get( SackConstants.VENDER ) );
            map.put( SackConstants.DATASOURCE_ID, row.get( SackConstants.DATASOURCE_ID ) );
            map.put( SackConstants.SCHEMA_NAME, row.get( SackConstants.SCHEMA_NAME ) );

            Configuration configuration = Configuration.create( map );
            DataSource dataSource = null;
            try
            {
                dataSource = DataSourceFactory.createDataSourceByConfiguration( configuration );
                dataSource.returnConnection( dataSource.getConnection() );
            }
            catch( Exception e )
            {
                dataSource = DataSourceFactory.createDataSourceByConfiguration( configuration, true );
                Tracer.debugTrace( DataSourceManager.class, "DataSourceManager : Create invalidate datasource"
                    + dataSource.getDriver() + " " + dataSource.getUserName() + " " + dataSource.getPassWord() + ""
                    + dataSource.getUrl(), null );
            }

            Tracer.debugTrace( DataSourceManager.class, " Load From DB : " + dataSource.toString(), null );

            dataSourceManager.dataSources.add( dataSource );
        }
    }

    public UserContext getUserContext()
    {
        return userContext;
    }

    /**
     * @param dataSourceId
     *            does not be used at present, so it can be <core>null</core> or whatever.
     * */
    public DataSource define( String url, String userName, String password, String driver, String dataSourceId )
    {
        Map<String, String> map = new HashMap<String, String>();
        map.put( SackConstants.URL, url );
        map.put( SackConstants.USER_NAME, userName );
        map.put( SackConstants.PASS_WORD, password );
        map.put( SackConstants.DRIVER, driver );
        Configuration configuration = Configuration.create( map );
        DataSource dataSource = DataSourceFactory.createDataSourceByConfiguration( configuration );

        for( DataSource d : dataSources )
        {
            if( d.toString().equals( dataSource.toString() ) )
            {
                Tracer.fatalTrace( getClass(), "Duplicated datasource defination : " + dataSource.toString(), null );
                throw new DuplicateException( "Duplicated datasource defination : " + dataSource.toString() );
            }
        }

        dataSources.add( dataSource );

        synchronize( this );

        return dataSource;
    }

    public DataSource define( DataSource dataSource )
    {
        
        dataSources.add( dataSource );
        synchronize( this );
        return dataSource;
    }

    public void discard( DataSource dataSource )
    {
        Tracer.debugTrace( getClass(), "Discard datasource by Id : " + dataSource.toString(), null );

        Iterator<DataSource> iterator = dataSources.iterator();
        //        synchronized( iterator )
        //        {
        //            while( iterator.hasNext() )
        //            {
        //                DataSource dataSource2 = iterator.next();
        //                if( dataSource2.toString().equals( dataSource.toString() ) )
        //                {
        //
        //                    dataSources.remove( dataSource2 );
        //                }
        //            }
        //        }

        DataSource dataSource2 = null;
        try
        {
            while( iterator.hasNext() )
            {
                dataSource2 = iterator.next();
                if( dataSource2.toString().equals( dataSource.toString() ) )
                {
                    dataSources.remove( dataSource2 );
                }
            }
        }
        catch( Exception e )
        {
            Tracer.fatalTrace( DataSourceManager.class, "Exception -> Force synchronization", e );
            dataSources.remove( dataSource2 );
        }

        synchronize( this );
    }

    public Set<DataSource> getDataSources()
    {
        return dataSources;
    }
}
